package com.hcj.springcloud.ProxyPattern.JdkAndCglibProxy;


import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.cglib.proxy.*;

import java.lang.reflect.Method;

/**
 * cglib和jdk代理的区别：
 * Java动态代理只能够对接口进行代理，不能对普通的类进行代理（因为所有生成的代理类的父类为Proxy，Java类继承机制不允许多重继承）；CGLIB能够代理普通类；
 * <p>
 * Java动态代理使用Java原生的反射API进行操作，在生成类上比较高效；CGLIB使用ASM框架直接对字节码进行操作，在类的执行过程中比较高效
 */
@DisplayName("代理测试")
public class ProxyTest {


    @DisplayName("测试1：未使用代理")
    @Test
    public void m1() {
        IService serviceA = new ServiceAImpl();
        IService serviceB = new ServiceBImpl();

        serviceA.m1();
        serviceA.m2();
        serviceA.m3();

        serviceB.m1();
        serviceB.m2();
        serviceB.m3();

    }

    @DisplayName("测试2：使用代理,扩展程序，使得程序能统计方法调用耗时情况")
    @Test
    public void m2() {
        IService serviceA = new ServiceAImpl();
        IService serviceB = new ServiceBImpl();

        ServiceProxy serviceProxyA = new ServiceProxy(serviceA);
        ServiceProxy serviceProxyB = new ServiceProxy(serviceB);

        serviceProxyA.m1();
        serviceProxyA.m2();
        serviceProxyA.m3();

        serviceProxyB.m1();
        serviceProxyB.m2();
        serviceProxyB.m3();

    }

    /**
     * jdk动态代理只能为接口创建代理，使用上有局限性。
     * 实际的场景中我们的类不一定有接口，
     * 此时如果我们想为普通的类也实现代理功能，
     * 我们就需要用到cglib来实现了。
     */
    @DisplayName("测试3：jdk proxy动态代理")
    @Test
    public void m3() {
        IService serviceA = new ServiceAImpl();
        IService serviceB = new ServiceBImpl();

        IService proxyA = CostTimeInvocationHandler.createProxy(serviceA, IService.class);
        IService proxyB = CostTimeInvocationHandler.createProxy(serviceB, IService.class);

        proxyA.m1();
        proxyA.m2();
        proxyA.m3();

        proxyB.m1();
        proxyB.m2();
        proxyB.m3();

    }

    /**
     * cglib是一个强大、高性能的字节码生成库，它用于在运行时扩展Java类和实现接口；
     * 本质上它是通过动态的生成一个子类去覆盖所要代理的类（非final修饰的类和方法）。
     * Enhancer可能是CGLIB中最常用的一个类，
     * 和jdk中的Proxy不同的是，
     * Enhancer既能够代理普通的class，也能够代理接口。
     * Enhancer创建一个被代理对象的子类并且拦截所有的方法调用
     * （包括从Object中继承的toString和hashCode方法）。
     * Enhancer不能够拦截final方法，
     * 例如Object.getClass()方法，这是由于Java final方法语义决定的。
     * 基于同样的道理，Enhancer也不能对final类进行代理操作。
     */
    @DisplayName("测试4：cglib proxy动态代理")
    @Test
    public void m4() {
        //1.创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //2.通过setSuperclass来设置父类型，即需要给哪个类创建代理类
        enhancer.setSuperclass(ServiceAImpl.class);
        /*3.设置回调，需实现org.springframework.cglib.proxy.Callback接口，
        此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor，也是一个接口，实现了Callback接口，
        当调用代理对象的任何方法的时候，都会被MethodInterceptor接口的invoke方法处理*/
        //enhancer.setCallback(NoOp.INSTANCE); // 直接放行，不做任何操做
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 代理对象方法拦截器
             * @param object 代理对象
             * @param method 被代理的类的方法，即Service1中的方法
             * @param params 调用方法传递的参数
             * @param methodProxy 方法代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object object, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
                System.out.println("调用的方法：" + method);
                //可以调用MethodProxy的invokeSuper调用被代理类的方法
                Object result = methodProxy.invokeSuper(object, params);
                return result;
            }
        });
        //4.获取代理对象,调用enhancer.create方法获取代理对象，这个方法返回的是Object类型的，所以需要强转一下
        IService proxy = (ServiceAImpl) enhancer.create();
        //5.调用代理对象的方法
        String s = proxy.m1();
        proxy.m2();
        proxy.m3();

    }

    @Test
    @DisplayName("cglib 案例:实现对任何类中的方法，执行耗时统计")
    public void m5() {
        IService serviceA = CostTimeProxyInterceptor.createProxy(new ServiceAImpl());
        IService serviceB = CostTimeProxyInterceptor.createProxy(new ServiceBImpl());
        serviceA.m1();
        serviceA.m2();
        serviceA.m3();

        serviceB.m1();
        serviceB.m2();
        serviceB.m3();


    }

    @Test
    @DisplayName("cglib 案例优化，实现对不同的方法用不同的回调函数")
    public void m6() {
        Enhancer enhancer = new Enhancer();
        //创建2个Callback
        Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
            long starTime = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            long endTime = System.nanoTime();
            System.out.println(method + "，耗时(纳秒):" + (endTime - starTime));
            return result;
        };
        //下面这个用来拦截方法后缀名1结尾的方法，返回固定值的
        Callback fixdValueCallback = (FixedValue) () -> "返回固定值的";

        CallbackHelper callbackHelper = new CallbackHelper(ServiceAImpl.class, new Class[]{IService.class}) {
            @Override
            protected Object getCallback(Method method) {
                return method.getName().endsWith("1") ? costTimeCallback : fixdValueCallback;
            }
        };


        enhancer.setSuperclass(ServiceAImpl.class);
        enhancer.setCallbacks(callbackHelper.getCallbacks());
        enhancer.setCallbackFilter(callbackHelper);

        ServiceAImpl proxy = (ServiceAImpl) enhancer.create();
        String m1_s = proxy.m1();
        String m2_s = proxy.m2();
        String m3_s = proxy.m3();
        System.out.println(m1_s);
        System.out.println(m2_s);
        System.out.println(m3_s);
    }


    @Test
    public void test() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ServiceAImpl.class);
        enhancer.setCallback(NoOp.INSTANCE);
        IService o = (ServiceAImpl) enhancer.create();
        o.m1();
        o.m2();
        o.m3();
    }
}
